package org.g4studio.core.mvc.xstruts.config;

import org.apache.commons.digester.AbstractObjectCreationFactory;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.Rule;
import org.apache.commons.digester.RuleSetBase;
import org.apache.commons.digester.SetPropertyRule;
import org.g4studio.core.mvc.xstruts.util.RequestUtils;
import org.xml.sax.Attributes;

/**
 * <p>
 * The set of Digester rules required to parse a Struts configuration file (
 * <code>struts-config.xml</code>).
 * </p>
 * 
 * @version $Rev: 421119 $ $Date: 2005-08-16 15:53:27 -0400 (Tue, 16 Aug 2005) $
 * @since Struts 1.1
 */
public class ConfigRuleSet extends RuleSetBase {
	// --------------------------------------------------------- Public Methods

	/**
	 * <p>
	 * Add the set of Rule instances defined in this RuleSet to the specified
	 * <code>Digester</code> instance, associating them with our namespace URI
	 * (if any). This method should only be called by a Digester instance. These
	 * rules assume that an instance of
	 * <code>org.g4studio.core.mvc.xstruts.config.ModuleConfig</code> is pushed
	 * onto the evaluation stack before parsing begins.
	 * </p>
	 * 
	 * @param digester
	 *            Digester instance to which the new Rule instances should be
	 *            added.
	 */
	public void addRuleInstances(Digester digester) {
		ClassLoader cl = digester.getClassLoader();

		digester.addRule("struts-config/action-mappings", new SetActionMappingClassRule());

		digester.addFactoryCreate("struts-config/action-mappings/action", new ActionMappingFactory(cl));
		digester.addSetProperties("struts-config/action-mappings/action");
		digester.addSetNext("struts-config/action-mappings/action", "addActionConfig",
				"org.g4studio.core.mvc.xstruts.config.ActionConfig");

		digester.addRule("struts-config/action-mappings/action/set-property", new BaseConfigSetPropertyRule());

		digester.addObjectCreate("struts-config/action-mappings/action/exception",
				"org.g4studio.core.mvc.xstruts.config.ExceptionConfig", "className");
		digester.addSetProperties("struts-config/action-mappings/action/exception");
		digester.addSetNext("struts-config/action-mappings/action/exception", "addExceptionConfig",
				"org.g4studio.core.mvc.xstruts.config.ExceptionConfig");

		digester.addRule("struts-config/action-mappings/action/exception/set-property", new BaseConfigSetPropertyRule());

		digester.addFactoryCreate("struts-config/action-mappings/action/forward", new ActionForwardFactory(cl));
		digester.addSetProperties("struts-config/action-mappings/action/forward");
		digester.addSetNext("struts-config/action-mappings/action/forward", "addForwardConfig",
				"org.g4studio.core.mvc.xstruts.config.ForwardConfig");

		digester.addRule("struts-config/action-mappings/action/forward/set-property", new BaseConfigSetPropertyRule());

		digester.addObjectCreate("struts-config/controller", "org.g4studio.core.mvc.xstruts.config.ControllerConfig",
				"className");
		digester.addSetProperties("struts-config/controller");
		digester.addSetNext("struts-config/controller", "setControllerConfig",
				"org.g4studio.core.mvc.xstruts.config.ControllerConfig");

		digester.addRule("struts-config/controller/set-property", new BaseConfigSetPropertyRule());

		digester.addRule("struts-config/form-beans", new SetActionFormBeanClassRule());

		digester.addFactoryCreate("struts-config/form-beans/form-bean", new ActionFormBeanFactory(cl));
		digester.addSetProperties("struts-config/form-beans/form-bean");
		digester.addSetNext("struts-config/form-beans/form-bean", "addFormBeanConfig",
				"org.g4studio.core.mvc.xstruts.config.FormBeanConfig");

		digester.addObjectCreate("struts-config/form-beans/form-bean/form-property",
				"org.g4studio.core.mvc.xstruts.config.FormPropertyConfig", "className");
		digester.addSetProperties("struts-config/form-beans/form-bean/form-property");
		digester.addSetNext("struts-config/form-beans/form-bean/form-property", "addFormPropertyConfig",
				"org.g4studio.core.mvc.xstruts.config.FormPropertyConfig");

		digester.addRule("struts-config/form-beans/form-bean/form-property/set-property",
				new BaseConfigSetPropertyRule());

		digester.addRule("struts-config/form-beans/form-bean/set-property", new BaseConfigSetPropertyRule());

		digester.addObjectCreate("struts-config/global-exceptions/exception",
				"org.g4studio.core.mvc.xstruts.config.ExceptionConfig", "className");
		digester.addSetProperties("struts-config/global-exceptions/exception");
		digester.addSetNext("struts-config/global-exceptions/exception", "addExceptionConfig",
				"org.g4studio.core.mvc.xstruts.config.ExceptionConfig");

		digester.addRule("struts-config/global-exceptions/exception/set-property", new BaseConfigSetPropertyRule());

		digester.addRule("struts-config/global-forwards", new SetActionForwardClassRule());

		digester.addFactoryCreate("struts-config/global-forwards/forward", new GlobalForwardFactory(cl));
		digester.addSetProperties("struts-config/global-forwards/forward");
		digester.addSetNext("struts-config/global-forwards/forward", "addForwardConfig",
				"org.g4studio.core.mvc.xstruts.config.ForwardConfig");

		digester.addRule("struts-config/global-forwards/forward/set-property", new BaseConfigSetPropertyRule());

		digester.addObjectCreate("struts-config/message-resources",
				"org.g4studio.core.mvc.xstruts.config.MessageResourcesConfig", "className");
		digester.addSetProperties("struts-config/message-resources");
		digester.addSetNext("struts-config/message-resources", "addMessageResourcesConfig",
				"org.g4studio.core.mvc.xstruts.config.MessageResourcesConfig");

		digester.addRule("struts-config/message-resources/set-property", new BaseConfigSetPropertyRule());

		digester.addObjectCreate("struts-config/plug-in", "org.g4studio.core.mvc.xstruts.config.PlugInConfig");
		digester.addSetProperties("struts-config/plug-in");
		digester.addSetNext("struts-config/plug-in", "addPlugInConfig",
				"org.g4studio.core.mvc.xstruts.config.PlugInConfig");

		digester.addRule("struts-config/plug-in/set-property", new PlugInSetPropertyRule());

		// PluginConfig does not extend BaseConfig, at least for now.
	}
}

/**
 * <p>
 * Class that records the name and value of a configuration property to be used
 * in configuring a <code>PlugIn</code> instance when instantiated.
 * </p>
 */
final class PlugInSetPropertyRule extends Rule {
	public PlugInSetPropertyRule() {
		super();
	}

	public void begin(String namespace, String names, Attributes attributes) throws Exception {
		PlugInConfig plugInConfig = (PlugInConfig) digester.peek();

		plugInConfig.addProperty(attributes.getValue("property"), attributes.getValue("value"));
	}
}

final class SetActionFormBeanClassRule extends Rule {
	public SetActionFormBeanClassRule() {
		super();
	}

	public void begin(String namespace, String name, Attributes attributes) throws Exception {
		String className = attributes.getValue("type");

		if (className != null) {
			ModuleConfig mc = (ModuleConfig) digester.peek();

			mc.setActionFormBeanClass(className);
		}
	}
}

/**
 * <p>
 * A variant of the standard Digester <code>SetPropertyRule</code>. If the
 * element being processed has a "key" attribute, then the value will be used to
 * call <code>setProperty(key,value)</code> on the object on top of the stack,
 * which will be assumed to be of type <code>ActionConfig</code>. Otherwise, the
 * standard <code>SetPropertyRule</code> behavior is invoked, and the value will
 * be used to set a bean property on the object on top of the Digester stack. In
 * that case, the element being processed is assumed to have attributes
 * "property" and "value".
 * </p>
 */
final class BaseConfigSetPropertyRule extends SetPropertyRule {
	public BaseConfigSetPropertyRule() {
		super("property", "value");
	}

	public void begin(Attributes attributes) throws Exception {
		if (attributes.getIndex("key") == -1) {
			super.begin(attributes);

			return;
		}

		if (attributes.getIndex("property") != -1) {
			throw new IllegalArgumentException("<set-property> accepts only one of 'key' or 'property' attributes.");
		}

		Object topOfStack = digester.peek();

		if (topOfStack instanceof BaseConfig) {
			BaseConfig config = (BaseConfig) topOfStack;

			config.setProperty(attributes.getValue("key"), attributes.getValue("value"));
		} else {
			throw new IllegalArgumentException(
					"'key' attribute of <set-property> only applicable to subclasses of BaseConfig; "
							+ "object on top of stack is " + topOfStack + " [key: " + attributes.getValue("key")
							+ ", value: " + attributes.getValue("value") + "]");
		}
	}
}

final class ActionFormBeanFactory extends AbstractObjectCreationFactory {
	private ClassLoader cl;

	public ActionFormBeanFactory(ClassLoader cl) {
		super();
		this.cl = cl;
	}

	public Object createObject(Attributes attributes) {
		// Identify the name of the class to instantiate
		String className = attributes.getValue("className");

		if (className == null) {
			ModuleConfig mc = (ModuleConfig) digester.peek();

			className = mc.getActionFormBeanClass();
		}

		// Instantiate the new object and return it
		Object actionFormBean = null;

		try {
			actionFormBean = RequestUtils.applicationInstance(className, cl);
		} catch (Exception e) {
			digester.getLogger().error("ActionFormBeanFactory.createObject: ", e);
		}

		return actionFormBean;
	}
}

final class SetActionMappingClassRule extends Rule {
	public SetActionMappingClassRule() {
		super();
	}

	public void begin(String namespace, String name, Attributes attributes) throws Exception {
		String className = attributes.getValue("type");

		if (className != null) {
			ModuleConfig mc = (ModuleConfig) digester.peek();

			mc.setActionMappingClass(className);
		}
	}
}

/**
 * <p>
 * An object creation factory which creates action mapping instances, taking
 * into account the default class name, which may have been specified on the
 * parent element and which is made available through the object on the top of
 * the stack, which must be a
 * <code>org.g4studio.core.mvc.xstruts.config.ModuleConfig</code>.
 * </p>
 */
final class ActionMappingFactory extends AbstractObjectCreationFactory {
	private ClassLoader cl;

	public ActionMappingFactory(ClassLoader cl) {
		super();
		this.cl = cl;
	}

	public Object createObject(Attributes attributes) {
		// Identify the name of the class to instantiate
		String className = attributes.getValue("className");

		if (className == null) {
			ModuleConfig mc = (ModuleConfig) digester.peek();

			className = mc.getActionMappingClass();
		}

		// Instantiate the new object and return it
		Object actionMapping = null;

		try {
			actionMapping = RequestUtils.applicationInstance(className, cl);
		} catch (Exception e) {
			digester.getLogger().error("ActionMappingFactory.createObject: ", e);
		}

		return actionMapping;
	}
}

/**
 * <p>
 * Class that sets the name of the class to use when creating global forward
 * instances. The value is set on the object on the top of the stack, which must
 * be a <code>org.g4studio.core.mvc.xstruts.config.ModuleConfig</code>.
 * </p>
 */
final class SetActionForwardClassRule extends Rule {
	public SetActionForwardClassRule() {
		super();
	}

	public void begin(String namespace, String name, Attributes attributes) throws Exception {
		String className = attributes.getValue("type");

		if (className != null) {
			ModuleConfig mc = (ModuleConfig) digester.peek();

			mc.setActionForwardClass(className);
		}
	}
}

/**
 * <p>
 * An object creation factory which creates global forward instances, taking
 * into account the default class name, which may have been specified on the
 * parent element and which is made available through the object on the top of
 * the stack, which must be a
 * <code>org.g4studio.core.mvc.xstruts.config.ModuleConfig</code>.
 * </p>
 */
final class GlobalForwardFactory extends AbstractObjectCreationFactory {
	private ClassLoader cl;

	public GlobalForwardFactory(ClassLoader cl) {
		super();
		this.cl = cl;
	}

	public Object createObject(Attributes attributes) {
		// Identify the name of the class to instantiate
		String className = attributes.getValue("className");

		if (className == null) {
			ModuleConfig mc = (ModuleConfig) digester.peek();

			className = mc.getActionForwardClass();
		}

		// Instantiate the new object and return it
		Object globalForward = null;

		try {
			globalForward = RequestUtils.applicationInstance(className, cl);
		} catch (Exception e) {
			digester.getLogger().error("GlobalForwardFactory.createObject: ", e);
		}

		return globalForward;
	}
}

/**
 * <p>
 * An object creation factory which creates action forward instances, taking
 * into account the default class name, which may have been specified on the
 * parent element and which is made available through the object on the top of
 * the stack, which must be a
 * <code>org.g4studio.core.mvc.xstruts.config.ModuleConfig</code>.
 * </p>
 */
final class ActionForwardFactory extends AbstractObjectCreationFactory {
	private ClassLoader cl;

	public ActionForwardFactory(ClassLoader cl) {
		super();
		this.cl = cl;
	}

	public Object createObject(Attributes attributes) {
		// Identify the name of the class to instantiate
		String className = attributes.getValue("className");

		if (className == null) {
			ModuleConfig mc = (ModuleConfig) digester.peek(1);

			className = mc.getActionForwardClass();
		}

		// Instantiate the new object and return it
		Object actionForward = null;

		try {
			actionForward = RequestUtils.applicationInstance(className, cl);
		} catch (Exception e) {
			digester.getLogger().error("ActionForwardFactory.createObject: ", e);
		}

		return actionForward;
	}
}
